package com.liuwei.msmall.search.service.impl;

import com.alibaba.fastjson.JSON;
import com.liuwei.msmall.common.constant.EsConstant;
import com.liuwei.msmall.common.dto.es.EsSkuModelDTO;
import com.liuwei.msmall.search.config.ElasticSearchConfig;
import com.liuwei.msmall.search.service.EsSearchService;
import com.liuwei.msmall.search.vo.SearchParmsVo;
import com.liuwei.msmall.search.vo.SearchRespVo;
import lombok.extern.slf4j.Slf4j;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.NestedQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.RangeQueryBuilder;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.nested.NestedAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @Classname SearchServiceImpl
 * @Date 2021/4/21 17:19
 * @Created by 刘
 */
@Slf4j
@Service
public class EsSearchServiceImpl implements EsSearchService {


    @Autowired
    private RestHighLevelClient client;



    @Override
    public SearchRespVo search(SearchParmsVo searchParmsVo) {

        SearchRespVo respVo=null;
        //构造检索条件
        SearchRequest searchRequest=bulidSearchRequst(searchParmsVo);
        //拿到返回结果
        SearchResponse response=null;
        try {
            response= client.search(searchRequest, ElasticSearchConfig.COMMON_OPTIONS);
        } catch (IOException e) {
            e.printStackTrace();
        }

        //解析并封装返回结果
        respVo=bulidSearchResult(response,searchParmsVo);

        return respVo;
    }

    /**
     * 将es返回的数据封装成自己想要的数据
     * @param response
     * @param searchParmsVo
     * @return
     */
    private SearchRespVo bulidSearchResult(SearchResponse response, SearchParmsVo searchParmsVo) {

        SearchRespVo respVo = new SearchRespVo();


        //拿到聚合结果
        Aggregations aggregations = response.getAggregations();
        //拿到品牌聚合结果
        ParsedLongTerms brandAggs = aggregations.get("brandAggs");
        List<SearchRespVo.Brand> brands=new ArrayList<>();
        for (Terms.Bucket bucket : brandAggs.getBuckets()) {

            SearchRespVo.Brand brand = new SearchRespVo.Brand();
            //品牌id
            long brandId = bucket.getKeyAsNumber().longValue();
            //品牌图片
            ParsedStringTerms brandImgAgg = bucket.getAggregations().get("brandImgAgg");
            String brandImg = brandImgAgg.getBuckets().get(0).getKeyAsString();
            //品牌名字
            ParsedStringTerms brandNameAgg = bucket.getAggregations().get("brandNameAgg");
            String brandName = brandNameAgg.getBuckets().get(0).getKeyAsString();
            brand.setBrandId(brandId);
            brand.setBrandImg(brandImg);
            brand.setBrandName(brandName);
            brands.add(brand);
        }
        respVo.setBrands(brands);


        //拿到聚合属性相关信息
        ParsedNested attrsAggs = aggregations.get("attrsAggs");
        List<SearchRespVo.AttrVo> attrVos=new ArrayList<>();
        ParsedLongTerms attr_id_aggs = attrsAggs.getAggregations().get("attr_id_aggs");
        for (Terms.Bucket bucket : attr_id_aggs.getBuckets()) {
            SearchRespVo.AttrVo attrVo = new SearchRespVo.AttrVo();
            //拿到属性id
            long attrId = bucket.getKeyAsNumber().longValue();
            ParsedStringTerms attr_name_aggs = bucket.getAggregations().get("attr_name_aggs");
            //拿到属性名
            String attrName = attr_name_aggs.getBuckets().get(0).getKeyAsString();

            //拿到属性对应的属性值
            ParsedStringTerms attr_value_aggs = bucket.getAggregations().get("attr_value_aggs");
            List<String> attrValues = attr_value_aggs.getBuckets().stream().map(item ->item.getKeyAsString())
                    .collect(Collectors.toList());
            attrVo.setAttrId(attrId);
            attrVo.setAttrName(attrName);
            attrVo.setAttrValue(attrValues);
            attrVos.add(attrVo);
        }
        respVo.setAttrVos(attrVos);


        //拿到分类的聚合信息
        ParsedLongTerms catalogAggs = aggregations.get("catalogAggs");
        List<SearchRespVo.Catalog> catalogs=new ArrayList<>();
        for (Terms.Bucket bucket : catalogAggs.getBuckets()) {
            //分类id
            long catalogId = bucket.getKeyAsNumber().longValue();
            //分类名字
            ParsedStringTerms catalogNameAggs = bucket.getAggregations().get("catalogNameAggs");
            String catalogName = catalogNameAggs.getBuckets().get(0).getKeyAsString();
            SearchRespVo.Catalog catalog = new SearchRespVo.Catalog();
            catalog.setCatelogId(catalogId);
            catalog.setCatelogName(catalogName);
            catalogs.add(catalog);
        }
        respVo.setCatalogs(catalogs);

        //封装检索出来的商品信息
        SearchHits hits = response.getHits();
        if (hits.getHits()!=null&&hits.getHits().length>0){
            List<EsSkuModelDTO> skuModels=new ArrayList<>();
            for (SearchHit searchHit : hits.getHits()) {

                String sourceAsString = searchHit.getSourceAsString();
                EsSkuModelDTO skuModel = JSON.parseObject(sourceAsString, EsSkuModelDTO.class);
                //判断是否有全文关键字查询
                if (!StringUtils.isEmpty(searchParmsVo.getKeyword())){
                    HighlightField skuTitle = searchHit.getHighlightFields().get("skuTitle");
                    String s = skuTitle.getFragments()[0].string();
                    //设置高亮关键子
                    skuModel.setSkuTitle(s);
                }
                skuModels.add(skuModel);
            }
            respVo.setSkuModels(skuModels);
        }

        //封装分页信息
        long total = response.getHits().getTotalHits().value;
        respVo.setTotal(total);
        respVo.setCurrentPage(searchParmsVo.getPageNum());
        int totalPages = (int)total%EsConstant.PRODUCT_PAGESIZE==0?((int) total/EsConstant.PRODUCT_PAGESIZE):((int)total/EsConstant.PRODUCT_PAGESIZE+1);


        //封装全部页码
        List<Integer> pageNavs=new ArrayList<>();
        for (int i=1;i<=totalPages;i++){
            pageNavs.add(i);
        }

        respVo.setTotalPages(totalPages);
        respVo.setPageNavs(pageNavs);

        return respVo;
    }

    /**
     * 构造es搜索请求
     * @return
     */
    private SearchRequest bulidSearchRequst(SearchParmsVo searchVo) {

        //拿到searchRequest
        SearchRequest searchRequest = new SearchRequest(EsConstant.INDEX_NAME);
        SearchSourceBuilder builder = new SearchSourceBuilder();

        // TODO 1.准备查询条件
        //构建bool组合查询
        BoolQueryBuilder queryBuilders = QueryBuilders.boolQuery();
        //全文匹配关键字
        if (!StringUtils.isEmpty(searchVo.getKeyword())){
            queryBuilders.must(QueryBuilders.matchQuery("skuTitle",searchVo.getKeyword()));
        }
        //查询品牌
        if (searchVo.getBrandIds()!=null&&searchVo.getBrandIds().size()>0){
            queryBuilders.filter(QueryBuilders.termsQuery("brandId",searchVo.getBrandIds()));
        }
        //查询分类
        if (searchVo.getCateId()!=null){
            queryBuilders.filter(QueryBuilders.termQuery("catalogId",searchVo.getCateId()));
        }
        //是否有库存, 1代表有库存，0代表没有
        if (searchVo.getHasStock()!=null){
            queryBuilders.filter(QueryBuilders.termQuery("hasStock",searchVo.getHasStock()==1));
        }

        //2.价格区间 skuPrice=500_(大于500)，400_500(400到500)，_500(小于500)
        if (!StringUtils.isEmpty(searchVo.getPriceRange())){
            String skuPrice = searchVo.getPriceRange();
            RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery("skuPrice");

            String[] s = skuPrice.split("_");
            if (s.length==2){
                queryBuilders.filter(rangeQueryBuilder.gte(s[0]).lte(s[1]));
            }else if (s.length==1){
                if (skuPrice.startsWith("_")){
                    queryBuilders.filter(rangeQueryBuilder.lte(s[0]));
                }
                if (skuPrice.endsWith("_")){
                    queryBuilders.filter(rangeQueryBuilder.gte(s[0]));
                }
            }

        }

        //查询属性， 属性值 attrs=1_5G:6G&attrs=2_其他&attrs=3_5寸:6寸，(表示选中属性id为1的，属性值为5G，6G)
        if (searchVo.getAttrs()!=null&&searchVo.getAttrs().size()>0){
            List<String> attrs = searchVo.getAttrs();
            //每个attr都必须生成一个nestedQuery,用户可能选择多个属性匹配
            for (String attr : attrs) {
                //构造组合查询
                BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
                String[] split = attr.split("_");
                String attrId=split[0];
                String[] attrValues = split[1].split(":");

                boolQuery.must(QueryBuilders.termQuery("attrs.attrId",attrId)).must(QueryBuilders.termsQuery("attrs.attrValue",attrValues));
                //String path, QueryBuilder query, ScoreMode scoreMode
                NestedQueryBuilder nestedQuery = QueryBuilders.nestedQuery("attrs", boolQuery, ScoreMode.None);

                queryBuilders.filter(nestedQuery);
            }

        }
        builder.query(queryBuilders);

        // TODO 3.准备排序字段,分页，高亮
        //排序条件 热度评分(hotSocre_desc/asc)，销量(saleCount_desc/asc)，价格(skuPrice_desc/asc)
        //3.1 sort=hotSocre_desc/asc
        if (!StringUtils.isEmpty(searchVo.getSort())){
            String sortStr = searchVo.getSort();
            String[] split = sortStr.split("_");
            builder.sort(split[0],split[1].equalsIgnoreCase("desc")? SortOrder.DESC:SortOrder.ASC);
        }
        //3.2分页
        // pageNum 1 form 0 size 2
        // pageNum 2 form 2 size 2
        //pageNum 3  form 4 size 2
        //form = (pageNum-1)*size
        builder.from((searchVo.getPageNum()-1)*EsConstant.PRODUCT_PAGESIZE);
        builder.size(EsConstant.PRODUCT_PAGESIZE);
        //3.3高亮
        if (!StringUtils.isEmpty(searchVo.getKeyword())){
            //关键字不为空时才需要高亮
            HighlightBuilder highlightBuilder = new HighlightBuilder();
            highlightBuilder.field("skuTitle");
            highlightBuilder.postTags("</b>");
            highlightBuilder.preTags("<b style='color:red;'>");
            builder.highlighter(highlightBuilder);
        }


        //TODO 4.准备聚合分析

        //4.1按照分类id聚合
        TermsAggregationBuilder catalogAggs = AggregationBuilders.terms("catalogAggs").field("catalogId").size(10);
        //构造分类子聚合
        catalogAggs.subAggregation(AggregationBuilders.terms("catalogNameAggs").field("catalogName").size(10));
        builder.aggregation(catalogAggs);

        //4.2按照品牌id聚合
        TermsAggregationBuilder brandAggs = AggregationBuilders.terms("brandAggs").field("brandId").size(10);
        //构造品牌2个子聚合
        brandAggs.subAggregation(AggregationBuilders.terms("brandNameAgg").field("brandName").size(1));
        brandAggs.subAggregation(AggregationBuilders.terms("brandImgAgg").field("brandImg").size(1));
        builder.aggregation(brandAggs);

        //4.3按照属性，属性值聚合
        NestedAggregationBuilder attrsAggs = AggregationBuilders.nested("attrsAggs", "attrs");

        //构造按照attrId的子聚合
        TermsAggregationBuilder attr_id_aggs= AggregationBuilders.terms("attr_id_aggs").field("attrs.attrId").size(10);

        //构造根据attrId查出的属性名，及对应的属性值2个子聚合
        attr_id_aggs.subAggregation(AggregationBuilders.terms("attr_name_aggs").field("attrs.attrName").size(1));
        attr_id_aggs.subAggregation(AggregationBuilders.terms("attr_value_aggs").field("attrs.attrValue").size(10));
        attrsAggs.subAggregation(attr_id_aggs);
        builder.aggregation(attrsAggs);

        log.info("构建的dsl语句："+builder.toString());
        //构建条件
        searchRequest.source(builder);

        return searchRequest;

    }

}
